import { errorMapper } from './errorMapper'
import { name as bName, version } from '../../package.json'
import {
    LifeCycles,
    AyakaOptions,
    AppLifeCycle,
    LifeCycleName,
    LifeCycleNames,
    AyakaMemory,
    MemoryCacher,
    RoomLevel,
    RoomLevelChangeCallback,
    TimerCallback,
} from './types'
import { colorful } from './utils/console'
import { isInterval, isMyRoom } from './utils/common'

let myName: string | undefined = undefined

/**
 * Ayaka 框架的入口
 */
export default class Ayaka {
    /**
     * 框架的配置项
     */
    public opts: AyakaOptions

    /**
     * Memory 缓存存放处
     */
    protected cachedMemory?: Memory

    /**
     * Memory 缓存器
     */
    protected memoryCacher?: MemoryCacher

    /**
     * cpu 统计
     */
    protected usedCpu: number[] = []

    /**
     * 房间等级缓存
     */
    protected roomLevels: RoomLevel = {}

    /**
     * 房间等级变化回调函数缓存
     */
    protected static roomLevelChangeCallbacks: RoomLevelChangeCallback[] = []

    /**
     * 定时器
     */
    protected static timer: { [time: number]: TimerCallback[] } = {}

    /**
     * 我的名字（玩家名）
     */
    public get myName() {
        if (myName) return myName
        return (myName = Object.values(Game.rooms).find((r) => isMyRoom(r))?.controller?.owner?.username)
    }

    /**
     * 生命周期
     */
    protected lifecycle: AppLifeCycle = {
        mounted: [],
        tickStart: [],
        tickEnd: [],
    }

    /**
     * 是否挂载完成
     */
    public isMounted = false

    public constructor(opts: AyakaOptions = {}) {
        // 挂载配置项
        this.opts = opts
        // 初始化
        this.init()
        // 挂载全局
        global.ayaka = this
        global.addTimer = Ayaka.addTimer
        // 绑定 this
        this.bindThis()
    }

    /**
     * 初始化
     */
    protected init() {
        this.clearOldBotMemory()

        const botName = `${this.opts.botName || bName}@${version}`
        const newMemory: AyakaMemory = {
            botName,
            mountedTime: Game.time,
        }
        const { botName: oldbotName, mountedTime: lastMountedTime } = Memory.ayaka || {}
        if (oldbotName && oldbotName !== botName) {
            console.log(colorful(`[init] bot更新 ${oldbotName} -> ${botName}`, 'green', true))
        }
        if (lastMountedTime) {
            console.log(colorful(`[init] 距上次挂载经过 ${Game.time - lastMountedTime}t`, 'green', true))
        }
        // 覆盖原先配置
        Memory.ayaka = Object.assign(Memory.ayaka || {}, newMemory)

        if (!Memory.switchs) Memory.switchs = {}
        if (!Memory.meta) Memory.meta = {}
    }

    /**
     * 绑定 this
     */
    public bindThis() {
        this.run = this.run.bind(this)
        this.on = this.on.bind(this)
        this.clearExpiredMemory = this.clearExpiredMemory.bind(this)
    }

    /**
     * 框架运行
     */
    public run() {
        if (this.memoryCacher) this.memoryCacher(this._run)
        else this._run()
    }

    protected _run = () => {
        errorMapper(() => {
            if (!this.mount()) return

            const { roomRunner, creepRunner, powerCreepRunner, globalRunner } = this.opts
            // 执行 tickStart 生命周期
            this.call('tickStart')

            if (globalRunner) globalRunner()
            if (roomRunner) Object.values(Game.rooms).map((room) => roomRunner(room))
            if (creepRunner) Object.values(Game.creeps).map((creep) => creepRunner(creep))
            if (powerCreepRunner) Object.values(Game.powerCreeps).map((creep) => powerCreepRunner(creep))
            this.updateRoomLevel()

            Ayaka.runTimer()

            // 执行 tickEnd 生命周期
            this.call('tickEnd')
            // 清理过期 memory
            this.clearExpiredMemory(this.opts.clearExpiredMemory)
            // 统计 cpu
            this.statAveCpu()
        })
    }

    /**
     * 添加生命周期回调
     */
    public on(lifeCycles: LifeCycles) {
        for (const name of LifeCycleNames) {
            const callback = lifeCycles[name]
            if (callback) this.lifecycle[name].push(callback)
        }
    }

    /**
     * 执行挂载生命周期回调
     * @returns 已挂载完成则返回 true，否则返回 false
     */
    public mount() {
        if (this.isMounted) return true
        // bucket 中有足够 cpu 才执行挂载？现在看来没有必要
        let { minCpu = 50 } = this.opts
        // 跨 shard 时最需 5 cpu 即可
        // if (!Object.keys(Game.spawns).length) {
        //     minCpu = 5
        // }
        minCpu = 0
        if (Game.cpu.bucket >= minCpu) {
            const cpu = Game.cpu.getUsed()
            // Memory 仙术
            this.enableMemoryCache = !!Memory.ayaka.enableMemoryCache
            // 执行挂载生命周期
            this.call('mounted')
            // 挂载完成
            this.isMounted = true
            // 打印挂载消耗的cpu
            const usedCpu = Game.cpu.getUsed() - cpu
            console.log(colorful(`[mount] 挂载完成，消耗 ${usedCpu} cpu`, 'green', true))
        }
        return false
    }

    /**
     * 执行生命周期回调
     */
    protected call(name: LifeCycleName) {
        const callbacks = this.lifecycle[name]
        if (!callbacks) return

        callbacks.forEach((callback) => callback())
    }

    /**
     * 是否启用 memory 缓存
     */
    public get enableMemoryCache() {
        return !!Memory.ayaka.enableMemoryCache
    }

    /**
     * 设置是否启用 memory 缓存
     */
    public set enableMemoryCache(value: boolean) {
        Memory.ayaka.enableMemoryCache = !!value

        if (value) {
            this.memoryCacher = (next) => {
                if (this.cachedMemory) {
                    // @ts-ignore
                    delete global.Memory
                    // @ts-ignore
                    global.Memory = this.cachedMemory
                } else {
                    this.cachedMemory = Memory
                }
                next()
                // @ts-ignore
                RawMemory._parsed = global.Memory
                // RawMemory.set(JSON.stringify(global.Memory))
            }
        } else {
            this.memoryCacher = undefined
        }
    }

    /**
     * 清理旧 bot 的 memory，不需要清理 global，因为上传代码后会自动重置
     *
     * @param force 是否强制清理
     */
    public clearOldBotMemory(force?: boolean) {
        // 默认不清理之前使用Ayaka框架的bot
        if (Memory.ayaka && !force) return false

        // 如果memory之前被损坏则重置
        if (!Memory.rooms) Memory.rooms = {}
        if (!Memory.creeps) Memory.creeps = {}

        for (const key in Memory) {
            if (key === 'rooms' || key === 'creeps') Memory[key] = {}
            else delete Memory[key as keyof Memory]
        }
        return true
    }

    /**
     * 获取平均 cpu
     */
    public get aveCpu() {
        return Memory.ayaka.aveCpu || Game.cpu.limit
    }

    /**
     * 设置平均 cpu
     */
    public set aveCpu(value: number) {
        Memory.ayaka.aveCpu = value
    }

    /**
     * 统计平均 cpu
     */
    protected statAveCpu() {
        const usedCpu = this.usedCpu
        usedCpu.push(Game.cpu.getUsed())
        if (usedCpu.length >= 20) {
            // 当前采样cpu
            const sampleAveCpu = usedCpu.reduce((sum, cpu) => sum + cpu, 0) / usedCpu.length
            // 采样权重
            const w = 0.875
            // 更新平均cpu
            this.aveCpu = +(this.aveCpu * w + sampleAveCpu * (1 - w)).toFixed(2)
            // 清空采样
            this.usedCpu = []
        }
    }

    /**
     * 更新房间等级
     */
    protected updateRoomLevel() {
        // 没回调函数就不更新了
        if (!Ayaka.roomLevelChangeCallbacks.length) return

        Object.values(Game.rooms).forEach((room) => {
            if (!isMyRoom(room)) return
            const level = room.controller.level
            const oldLevel = this.roomLevels[room.name]
            // 说明是新房间，或者是初次挂载
            if (level !== oldLevel) {
                Ayaka.roomLevelChangeCallbacks.forEach((callback) => callback(room, level, oldLevel))
                this.roomLevels[room.name] = level
            }
        })
    }

    /**
     * 添加房间等级变化回调，不要在循环中添加！一个函数只能添加一次！
     */
    public static onRoomLevelChange(callback: RoomLevelChangeCallback) {
        Ayaka.roomLevelChangeCallbacks.push(callback)
    }

    /**
     * 添加定时函数，注意不要将一些关键函数放在定时器中，因为定时器不一定会执行，比如可能会全局重置导致定时器失效
     */
    public static addTimer(time: number, callback: TimerCallback) {
        if (!Ayaka.timer[Game.time + time]) {
            Ayaka.timer[Game.time + time] = [callback]
        } else {
            Ayaka.timer[Game.time + time].push(callback)
        }
    }

    /**
     * 添加下 1 tick执行的定时函数
     */
    public static nextTick(callback: TimerCallback) {
        Ayaka.addTimer(1, callback)
    }

    /**
     * 执行定时函数
     */
    public static runTimer() {
        const callbacks = Ayaka.timer[Game.time]
        if (!callbacks) return

        callbacks.forEach((callback) => callback())
        delete Ayaka.timer[Game.time]
    }

    /**
     * 清除过期爬，房间，旗帜的 memory
     * @param interval 清理间隔
     */
    public clearExpiredMemory(interval?: number) {
        if (typeof interval === 'undefined' || !isInterval(interval)) return

        for (const name in Memory.creeps) {
            if (!Game.creeps[name]) delete Memory.creeps[name]
        }
        for (const name in Memory.rooms) {
            if (!Game.rooms[name]) delete Memory.rooms[name]
        }
        for (const name in Memory.flags) {
            if (!Game.flags[name]) delete Memory.flags[name]
        }
    }
}

// @types/node@14时, typeof global == NodeJS.Global & typeof globalThis
declare global {
    namespace NodeJS {
        interface Global {
            ayaka: Ayaka
            addTimer: typeof Ayaka.addTimer
        }
    }
}
